﻿namespace WebPacker.Packs.Watchers
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using WebPacker.Packs.Resources;

	public sealed class CompositeWatcher : IResourceWatcher, IDisposable
	{
		private readonly IList<IResourceWatcher> watchers;
		private readonly EventHandler<ResourceChangedEventArgs> changedHandler;
		private readonly ResourceChangedEventQueue eventQueue;
		private bool disposed;

		public CompositeWatcher()
		{
			this.watchers = new List<IResourceWatcher>();
			this.changedHandler = new EventHandler<ResourceChangedEventArgs>(OnResourceChange);
			this.eventQueue = new ResourceChangedEventQueue();
			this.eventQueue.Changed += OnChanged;
		}

		public event EventHandler<ResourceChangedEventArgs> Changed;

		public int NotifyDelay
		{
			get { return eventQueue.NotifyDelay; }
			set { eventQueue.NotifyDelay = value; }
		}

		public bool Accept(IResource resource)
		{
			if (resource == null)
			{
				throw new ArgumentNullException("resource");
			}

			lock (watchers)
			{
				return watchers.Any(x => x.Accept(resource));
			}
		}

		public void Register(IEnumerable<IResourceWatcher> watchers)
		{
			if (watchers == null)
			{
				throw new ArgumentNullException("watchers");
			}

			foreach (var watcher in watchers)
			{
				Register(watcher);
			}
		}

		public void Register(IResourceWatcher watcher)
		{
			if (watcher == null)
			{
				throw new ArgumentNullException("watcher");
			}

			if (watcher == this)
			{
				throw new ArgumentException("Invalid watcher.");
			}

			lock (watchers)
			{
				if (watchers.Any(x => x == watcher))
				{
					throw new ArgumentException("Monitor is registered allready.");
				}

				watchers.Add(watcher);
				watcher.Changed += changedHandler;
			}
		}

		public bool Contains(IResource resource)
		{
			if (resource == null)
			{
				throw new ArgumentNullException("resource");
			}

			var contains = false;

			lock (watchers)
			{
				foreach (var watcher in watchers)
				{
					if (watcher.Contains(resource))
					{
						contains = true;
						break;
					}
				}
			}

			return contains;
		}

		public IEnumerable<IResource> GetResources()
		{
			var resources = new List<IResource>();

			lock (watchers)
			{
				foreach (var watcher in watchers)
				{
					resources.AddRange(watcher.GetResources());
				}
			}

			return resources;
		}

		public void Add(IResource resource)
		{
			if (resource == null)
			{
				throw new ArgumentNullException("resource");
			}

			GetWatcher(resource).Add(resource);
		}

		public bool Remove(IResource resource)
		{
			if (resource == null)
			{
				throw new ArgumentNullException("resource");
			}

			return GetWatcher(resource).Remove(resource);
		}

		public void Set(IEnumerable<IResource> resources)
		{
			if (resources == null)
			{
				throw new ArgumentNullException("resources");
			}

			foreach (var resource in resources)
			{
				if (resource == null)
				{
					throw new ArgumentNullException("resource");
				}

				GetWatcher(resource);
			}

			lock (watchers)
			{
				foreach (var watcher in watchers)
				{
					watcher.Set(resources.Where(x => watcher.Accept(x)));
				}
			}
		}

		public void Clear()
		{
			lock (watchers)
			{
				foreach (var watcher in watchers)
				{
					watcher.Clear();
				}
			}
		}

		public void Dispose()
		{
			if (!disposed)
			{
				eventQueue.Dispose();

				foreach (var watcher in watchers)
				{
					watcher.Clear();

					if (watcher is IDisposable)
					{
						((IDisposable)watcher).Dispose();
					}
				}

				disposed = true;
			}
		}

		private void OnResourceChange(object sender, ResourceChangedEventArgs e)
		{
			if (Changed != null)
			{
				eventQueue.AddEvent(e);
			}
		}

		private void OnChanged(object sender, ResourceChangedEventArgs e)
		{
			if (Changed != null)
			{
				Changed(this, e);
			}
		}

		private IResourceWatcher GetWatcher(IResource resource)
		{
			IResourceWatcher watcher;

			lock (watchers)
			{
				watcher = watchers.Where(x => x.Accept(resource)).FirstOrDefault();
			}

			if (watcher == null)
			{
				throw new ArgumentException(string.Format("Unsupported resource type \"{0}\".", resource.GetType()));
			}

			return watcher;
		}
	}
}
